package com.yunao.commons.serachcore.core.utils;

import com.yunao.commons.serachcore.core.bean.*;
import com.yunao.commons.serachcore.core.help.GroupField;
import com.yunao.commons.serachcore.core.query.Query;
import com.yunao.commons.utils.DomainSourceUtils;
import com.yunao.commons.utils.TextSummary;
import com.zhongsou.search.core.query.*;
import com.zhongsou.search.core.query.draw.WamCNode;
import com.zhongsou.search.core.query.draw.WamCalculator;
import org.apache.commons.lang3.StringUtils;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

import static java.util.stream.Collectors.toList;

public class ResultUtils {

	public static Articles parse(Hits hits, Query query) {
		Articles articles = new Articles(hits.getTotal());
		if (hits.size() > 0) {
			for (int i = 0, len = hits.size(); i < len; i++) {
				Article article = hitToArticle(hits.get(i), query);
				if (article == null) {
					return null;
				}
				articles.addData(article);
			}
		}
		return articles;
	}

	public static Groups parse(GroupHits hits, Query query) {
		Groups groups = new Groups(hits.getTotalOfArticle());
		if (hits.size() > 0) {
			List<Group> data = new ArrayList<>();
			for (int i = 0, len = hits.size(); i < len; i++) {
				GroupHit hit = hits.get(i);
				data.add(new Group(parseGroupKey(query.getGroup(), hit.getKey()), hit.getNum()));
			}

			Comparator<Group> comparator = isDateField(query.getGroup()) ? Comparator.comparing(Group::getKey)
					: Comparator.comparing(Group::getValue).reversed();
			int limit = query.getLimit() == 0 ? data.size() : query.getLimit();
			data.stream().sorted(comparator).limit(limit).forEach(groups::addData);
		}
		return groups;
	}

	private static String parseGroupKey(GroupField field, String key) {
		String res;
		if (GroupField.HOUR == field) {
			res = new Date(Long.parseLong(key)*1000)
					.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
					.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
		} else if (GroupField.DAY == field) {
			res = new Date(Long.parseLong(key)*1000)
					.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
					.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		} else if (GroupField.WEEK == field) {
			LocalDate date = new Date(Long.parseLong(key)*1000)
					.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
			res = date.with(TemporalAdjusters.previous(DayOfWeek.MONDAY))
					.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
					+ "," + date.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY))
					.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		} else {
			res = key;
		}
		return res;
	}

	private static boolean isDateField(GroupField gField) {
		return GroupField.HOUR.equals(gField) 
				|| GroupField.DAY.equals(gField) 
				|| GroupField.WEEK.equals(gField);
	}

	public static Cubes parse(CubeHits hits, Query query) {
		Cubes cubes = new Cubes(hits.getTotalOfArticle());
		if (hits.size() > 0) {
			Comparator<Group> comparator;
			int limit;
			if (GroupField.DOMAIN == query.getGroup2()) {
				comparator = Comparator.comparingLong(Group::getValue).reversed();
				limit = 10;
			} else {
				comparator = Comparator.comparing(Group::getKey);
				limit = 0;
			}
			
			List<Cube> data = new ArrayList<>();
			for (int i = 0, len = hits.size(); i < len; i++) {
				CubeHit hit = hits.get(i);
				Cube cube = new Cube(parseGroupKey(query.getGroup(), hit.getKey()), hit.getNum());
				hit.getSubHitList().stream().map(ghit -> toGroup(query.getGroup2(), ghit))
						.sorted(comparator).limit(limit == 0 ? hit.getSubHitNum() : limit).forEach(cube::addSub);
				data.add(cube);
			}

			data.stream().sorted(Comparator.comparing(Cube::getKey)).forEach(cubes::addData);

			hits.getSumHitsOf2ndField().stream().map(ghit -> toGroup(query.getGroup2(), ghit))
					.sorted(comparator).limit(limit == 0 ? hits.getTotalOf2stField() : limit).forEach(cubes::addSubs);
		}
		return cubes;
	}

	private static Group toGroup(GroupField field, GroupHit hit) {
		return new Group(parseGroupKey(field, hit.getKey()), hit.getNum());
	}

	public static Wordclouds parse(WordCloudHits hits, Query query) {
		Wordclouds wordclouds = new Wordclouds(hits.getDocTotal());
		if (hits.getWordnum() > 0) {
			for (int i = 0, len = hits.getWordnum(); i < len; i++) {
				DmkWord word = hits.getWord(i);
				if (!verifyKeywords(word.getWord(), query.getStopWords())) {
					continue;
				}
				Wordcloud wordcloud = new Wordcloud(word.getWord(), word.getDocnum());
				wordcloud.setIdf(word.getIdf());
				wordcloud.setMark(word.getMark());
				wordclouds.addData(wordcloud);
			}
		}
		Collections.sort(wordclouds.getData(), Comparator.comparingInt(Wordcloud::getDocnum).reversed());
		return wordclouds;
	}

	public static Wordrelations parse(WamHits hits, Query query) {
		Wordrelations wordrelations = new Wordrelations(hits.getDocTotal());
		String[] stopWords = {};
		if (hits.getWordnum() > 0) {
			for (int i = 0, len = hits.getWordnum(); i < len; i++) {
				DmkWord word = hits.getWord(i);
				if (!verifyKeywords(word.getWord(), query.getStopWords())) {
					stopWords = arrayAdd(stopWords, word.getWord());
				}
			}

			WamCalculator calculator = new WamCalculator();
			calculator.fillIn(hits, stopWords);
			@SuppressWarnings("unchecked")
			List<WamCNode> wamCNodes = calculator.getResult();
			if (!wamCNodes.isEmpty()) {
				Map<String, Integer> m = getKeyMap(wamCNodes);

				for (WamCNode node : wamCNodes) {
					Wordrelation wordrelation = wamnodeToWordrelation(node, m);
					wordrelations.addData(wordrelation);
				}
			}
		}
		return wordrelations;
	}
	
	static String[] arrayAdd(String[] array, String v) {
		String[] res = new String[array.length + 1];
		System.arraycopy(array, 0, res, 0, array.length);
		res[array.length] = v;
		return res;
	}

	public static Clusters parse(ClusterHits hits, Query query) {
		Clusters clusters = new Clusters(hits.getTotalDocNum());
		if (hits.getGroupNum() > 0) {
			for (ClusterGroup group : hits.getGroupList()) {
				Cluster cluster = new Cluster();
				cluster.setWordLimit(group.getWordLimit());
				cluster.setWordNum(group.getWordNum());
				cluster.setWords(group.getWordList());

				cluster.setDocPercent(group.getDocNumPercentage());
				cluster.setDocRadio(group.getDocRadio());
				List<Article> docs = group.getHitList().stream().map(hit -> hitToArticle(hit, query))
						.filter(Objects::nonNull).collect(toList());
				if (docs.size()>0)
					cluster.setDocs(Arrays.asList(new Article[]{ docs.get(0)}));
				else
					cluster.setDocs(docs);
				clusters.addData(cluster);
			}
		}
		return clusters;
	}

	private static Wordrelation wamnodeToWordrelation(WamCNode node, Map<String, Integer> m) {
		Wordrelation wordrelation = new Wordrelation();
		wordrelation.setId(m.get(node.getWord()));
		wordrelation.setKey(node.getWord());
		wordrelation.setDocnum(node.getDocnum());
		wordrelation.setIdf(node.getIdf());
		wordrelation.setMark(node.getMark());
		wordrelation.setRadius(node.getRadius());
		wordrelation.setAngle(node.getAngle());

		WamCNode parent = node.getParent();
		if (parent != null) {
			wordrelation.setParentId(m.get(parent.getWord()));
			wordrelation.setParentKey(parent.getWord());
		}
		return wordrelation;
	}

	private static Map<String, Integer> getKeyMap(List<WamCNode> wamCNodes) {
		Map<String, Integer> m = new HashMap<>();
		for (WamCNode node : wamCNodes) {
			int id = node.getWord().hashCode();
			String key = node.getWord();
			while (m.containsValue(id)) {
				id++;
				break;
			}
			m.put(key, id);
		}
		return m;
	}

	private static boolean verifyKeywords(String word, String[] stopWords) {
		if (stopWords == null || stopWords.length == 0) {
			return true;
		}
		for (String stopWord : stopWords) {
			if (stopWord.startsWith("%") && stopWord.endsWith("%")) {
				if (word.contains(substringBetween(stopWord, "%", "%"))) {
					return false;
				}
			} else if (stopWord.startsWith("%")) {
				if (word.endsWith(substringAfter(stopWord, "%"))) {
					return false;
				}
			} else if (stopWord.endsWith("%")) {
				if (word.startsWith(substringBeforeLast(stopWord, "%"))) {
					return false;
				}
			} else {
				if (word.equals(stopWord)) {
					return false;
				}
			}
		}
		return true;
	}
	
	private static String substringAfter(final String str, final String separator) {
        final int pos = str.indexOf(separator);
        if (pos == -1) {
            return "";
        }
        return str.substring(pos + separator.length());
    }
	
	private static String substringBeforeLast(final String str, final String separator) {
        final int pos = str.lastIndexOf(separator);
        if (pos == -1) {
            return str;
        }
        return str.substring(0, pos);
    }
	
	private static String substringBetween(final String str, final String open, final String close) {
        final int start = str.indexOf(open);
        if (start != -1) {
            final int end = str.indexOf(close, start + open.length());
            if (end != -1) {
                return str.substring(start + open.length(), end);
            }
        }
        return null;
    }
	
	static Article hitToArticle(Hit hit, Query query) {
		com.zhongsou.search.core.article.Article a;
		if (hit == null || (a = hit.getArticle()) == null) {
			return null;
		}
		Article article = new Article();
		if (hit.getDupNum() > 0) {
			@SuppressWarnings("unchecked")
			List<Hit> subHits = hit.getDupList();
			List<Article> subs = subHits.stream().map(subHit -> hitToArticle(subHit, query)).collect(toList());
			article.setSubs(subs);
		}
		article.setId(a.getLong("ID"));
		article.setTitle(a.getString("TI"));
		String tx = a.getString("TX");
		if (StringUtils.isEmpty(article.getTitle())){
			if (tx.length()>20)
				article.setTitle(tx.substring(0,20));
			else
				article.setTitle(tx.substring(0,tx.length()));
		}
		if (query != null && !query.isTrimArticle()) {
			article.setContent(tx);
		}
		String summary;
		if (query != null && query.getKeywords() != null) {
			summary = hit.getSummary("TX", query.getKeywords(), "<em>", "</em>", 200);
		} else {
			summary = TextSummary.summarise(tx);
		}
		article.setSummary(summary);
		article.setUrl(a.getString("UR"));
		article.setDatetime(a.getLong("RQ"));
		article.setPubdate(new Date(a.getLong("RQ")));
		article.setAuthor(a.getString("AU"));
		article.setImages(parseUrls(a.getString("IL")));
		article.setVideos(parseUrls(a.getString("VL")));
		article.setNewsSource(a.getString("AA"));
		article.setNewsClassify(a.getString("AG"));
		article.setDomain(a.getString("DM"));
		article.setDomainSource(DomainSourceUtils.getSource(a.getString("HO")));
		article.setMediaType(a.getString("MD"));
		article.setChannel(a.getString("CC"));
		article.setEmotion(a.getString("EM"));
		article.setAuthorType(a.getString("AT"));
		article.setMessageType(a.getString("MT"));
		article.setLocation(a.getString("LC"));
		article.setDomainLevel(a.getString("DL"));
		String sensitiveWords = a.getString("TT");
		if (sensitiveWords != null && !sensitiveWords.isEmpty() && !"未知".equals(sensitiveWords)) {
			article.setSensitiveWords(sensitiveWords);
		}
		article.setHighFrequencyWords(a.getString("01TX"));
		article.setDomestic(a.getInt("TE") == 1 ? 2 : 1);
		article.setViews(a.getLong("VN"));
		article.setComments(a.getLong("RN"));
		article.setLikes(a.getLong("ZN"));
		article.setReprints(a.getLong("PN"));
		article.setNr(a.getString("NR"));
		article.setNs(a.getString("NS"));
		article.setNt(a.getString("NT"));
		return article;
	}

	private static String parseUrls(String v) {
		if (v == null || v.isEmpty() || !(v.contains("thumb_url") && v.startsWith("[") && v.endsWith("]"))) {
			return v;
		}

		/*JSONArray array = new JSONArray(v);
		String[] res = new String[array.length()];
		for (int i = 0; i < array.length(); i++) {
			JSONObject obj = array.getJSONObject(i);
			res[i] = obj.getString("thumb_url");
		}
		return String.join(",", res);*/
		return v;
	}

}
